
package Controller;

import Model.Map;
import Model.Tile;

import java.awt.*;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Random;


public class MapGenerator
{
    private Map map;
   // private Tile[][] tileMap;
    public static int snowHeight,mountainHeight,plainsHeight,oceanHeight,plainsAmount,desertAmount,forestAmount,riverAmount,mountainAmount,minHeight;
    private int MTN_ZONE_RADIUS;

    private static LinkedList<Point> generationQueue;

    /**
     * This is called when the 'generate' button is pressed on the config screen. It passes in the values chosen by the
     * user which are then used to make a custom map generator around these conditions.
     * @param mysnowHeight  the max map height for the snow biome. everything below this will be snow
     * @param mymountainHeight  the max height for the mountain biome. everything below this but above plains will be mountain
     * @param myplainsHeight   the max height for the plains biome. everything between this and oceanHeight will
     *                       be plains
     * @param myoceanHeight    the max height for the ocean biome. everything between this and minHeight will
     *                       be ocean
     * @param myminHeight      the min height (a negative number, depth) of the map
     * @param myplainsAmount   represents the prevalence of plains, 0 for low, 1 for medium, 2 for high. higher values
     *                       mean that other biomes will not expand into plains as much.
     * @param mydesertAmount   represents the prevalence of deserts, 0 for low, 1 for medium, 2 for high. higher values
     *                       mean more desert pivots and/or deserts expand more.
     * @param myforestAmount   represents the prevalence of forests. higher values mean that forests spread farther away
     *                       from rivers.
     * @param myriverAmount    represents the prevalence of rivers. higher values mean more river pivots.
     * @param mymountainAmount represents the prevalence of mountains and hills. higher values mean more mountain pivots
     */
    public  MapGenerator(int mysnowHeight,int mymountainHeight,int myplainsHeight,int myoceanHeight,int myminHeight,
                         int myplainsAmount,int mydesertAmount,int myforestAmount,int myriverAmount,int mymountainAmount)
    {
        snowHeight = mysnowHeight;
        mountainHeight = mymountainHeight;
        plainsHeight = myplainsHeight;
        oceanHeight = myoceanHeight;
        plainsAmount = myplainsAmount;
        desertAmount = mydesertAmount;
        forestAmount = myforestAmount;
        riverAmount = myriverAmount;
        mountainAmount = mymountainAmount;
        minHeight = myminHeight;

        this.map = new Map();
        MTN_ZONE_RADIUS = (int)(map.getTotalTiles()*.20);   //mountain zone up to 40% of the map
    }


    public Map generate()
    {
        generationQueue = new LinkedList<Point>();

        //generate the mountain pivots
        ArrayList<Point> mountainPivots = this.generateMountainPivots();

        //add these points to the generationQueue
        for(Point p : mountainPivots)
        {
            generationQueue.add(p);
            //System.out.println(generationQueue.size());
        }

        //iterate the mountain pivots
        while(!generationQueue.isEmpty())
        {
            iterateMountain(generationQueue.poll());
            //System.out.println(generationQueue.size());
        }

        return this.map;
    }

    /**
     * This generates the pivots (point/tile pairs) to be used as anchors for drawing on the map.
     * Mountain zones -  squares determined by a randomly generated origin, with height of MTN_ZONE_RADIUS.
     *        Highest probability of mountain pivots
     * Number of zones determined by mountainAmount
     * Valley pivots?
     * River Sources determined by rivers amount
     * Biome Pivots
     * @return Returns an array of pivots (Point/Tile pairs) to be used for rendering
     */
    public ArrayList<Point> generateMountainPivots(){
        int numPivots = 30;

        //entered values in config are all relative.  Total percentage on map is determined by percentage by given numbers.
        //the height of a mountain determines its spread
        //river amount is not considered because it is a non-blocking element.
        double total = plainsAmount + mountainAmount;

        double mountainPercent = (double)mountainAmount/total;
        double plainsPercent = (double)plainsAmount/total;

        int mountainTotal = (int)(numPivots*mountainPercent);
        int plainsTotal = (int)(numPivots*plainsPercent);

        Random rand = new Random();
        Point mtnOrigin = new Point(rand.nextInt(map.getMaxCol()), rand.nextInt(map.getMaxRow()));

        //if radius overlaps edge, mountainPivots will appear somewhere other than mountain zones
        for (int i = 0; i < mountainTotal; i++)   {
            Point location = new Point((mtnOrigin.x + rand.nextInt(MTN_ZONE_RADIUS))%(map.getMaxCol()),
                    (mtnOrigin.y + rand.nextInt(MTN_ZONE_RADIUS))%map.getMaxRow());
            int height = rand.nextInt(snowHeight + minHeight) + mountainHeight - minHeight;

            Tile tile = new Tile(height);
            //determine mountain spreads - peak shape cases
            chooseShape(location, tile);
        }

        for (int i = 0; i < plainsTotal; i++)   {
            Point location = new Point((rand.nextInt(map.getMaxCol())), rand.nextInt(map.getMaxRow()));
            int height = rand.nextInt(mountainHeight + minHeight) - minHeight;

            Tile tile = new Tile(height);
            //determine mountain spreads - peak shape cases
            chooseShape(location, tile);
        }
        return this.map.pivots;
    }

    //takes in a pivot, returns the number of tiles created from this starting shape
    public void chooseShape( Point location, Tile tile)
    {
        int i = 0;
        Random rand = new Random();
        switch(rand.nextInt(5)){
            //switch(0){
            case 0:  //cluster 0 - one square - odd pyramid
                map.pivots.add(location);
                //map.tiles.put(location, tile);
                map.tiles[location.x][location.y] = tile;
                //System.out.println("one");
                break;
            case 1:  //cluster 1 -  two squares - up/down - even pyramid
                map.pivots.add(location);
                //map.tiles.put(location, tile);
                map.tiles[location.x][location.y] = tile;
                map.pivots.add(new Point(location.x, (location.y + 1)%map.getMaxRow()));
                //map.tiles.put(new Point(location.x, (location.y + 1)%map.getMaxRow()), tile);
                map.tiles[location.x][(location.y + 1)%map.getMaxRow()] = tile;
                //System.out.println("two");
                break;
            case 2:  //cluster 2 -  two squares - left/right - even pyramid
                map.pivots.add(location);
                //map.tiles.put(location, tile);
                map.tiles[location.x][location.y] = tile;
                map.pivots.add(new Point((location.x + 1)%map.getMaxCol(), location.y));
                //map.tiles.put(new Point((location.x + 1)%map.getMaxCol(), location.y), tile);
                map.tiles[(location.x + 1)%map.getMaxCol()][location.y] = tile;
                //System.out.println("three");
                break;
            case 3:  //cluster 3 -  four squares - even pyramid
                map.pivots.add(location);
                //map.tiles.put(location, tile);
                map.tiles[location.x][location.y] = tile;
                map.pivots.add(new Point((location.x + 1)%map.getMaxCol(), location.y));
                //map.tiles.put(new Point((location.x + 1)%map.getMaxCol(), location.y), tile);
                map.tiles[(location.x + 1)%map.getMaxCol()][location.y] = tile;
                map.pivots.add(new Point(location.x, (location.y + 1)%map.getMaxRow()));
                //map.tiles.put(new Point(location.x, (location.y + 1)%map.getMaxRow()), tile);
                map.tiles[location.x][(location.y + 1)%map.getMaxRow()] = tile;
                map.pivots.add(new Point((location.x + 1)%map.getMaxCol(), (location.y + 1)%map.getMaxRow()));
                //map.tiles.put(new Point((location.x + 1)%map.getMaxCol(), (location.y + 1)%map.getMaxRow()), tile);
                map.tiles[(location.x + 1)%map.getMaxCol()][(location.y + 1)%map.getMaxRow()] = tile;
                //System.out.println("four");
                break;
            case 4:  //cluster 4 - L-shape  - organic pyramid
                map.pivots.add(location);
                //map.tiles.put(location, tile);
                map.tiles[location.x][location.y] = tile;
                map.pivots.add(new Point(location.x + 1, location.y));
                //map.tiles.put(new Point(location.x + 1, location.y), tile);
                map.tiles[location.x + 1][location.y] = tile;
                map.pivots.add(new Point(location.x, location.y + 1));
                //map.tiles.put(new Point(location.x, location.y + 1), tile);
                map.tiles[location.x][location.y + 1] = tile;
                //System.out.println("five");
                break;
        }
    }

    public void iterateMountain(Point p)
    {
        //System.out.println("interate mountain "+ p.x+" "+p.y);
        //Tile t = map.tiles.get(p);
        Tile t = this.map.tiles[p.x][p.y];
        //System.out.println(t.getBiome().toString());
        Point upCoord = new Point(p.x,p.y-1);
        Point downCoord = new Point(p.x,p.y+1);
        Point leftCoord = new Point(p.x-1,p.y);
        Point rightCoord = new Point(p.x+1,p.y);
        if(upCoord.y >= 0)
        {
            if(this.map.tiles[upCoord.x][upCoord.y] == null || this.map.tiles[upCoord.x][upCoord.y].getHeight() < t.getHeight() - 10)
            {
                if(t.getHeight() - (snowHeight/30) > minHeight)
                {
                    //map.tiles.put(upCoord, new Tile(t.getHeight() - (snowHeight/30)));
                    this.map.tiles[upCoord.x][upCoord.y] = new Tile(t.getHeight() - 10);
                    generationQueue.add(upCoord);
                }
            }
        }
        if(downCoord.y < map.width)
        {
            if(this.map.tiles[downCoord.x][downCoord.y] == null  || this.map.tiles[downCoord.x][downCoord.y].getHeight() < t.getHeight() - 10)
            {
                if(t.getHeight() - (snowHeight/30) > minHeight)
                {
                    //map.tiles.put(downCoord, new Tile(t.getHeight() - (snowHeight/30)));
                    this.map.tiles[downCoord.x][downCoord.y] = new Tile(t.getHeight() - 10);
                    generationQueue.add(downCoord);
                }
            }
        }
        if(leftCoord.x >= 0)
        {
            if(this.map.tiles[leftCoord.x][leftCoord.y] == null || this.map.tiles[leftCoord.x][leftCoord.y].getHeight() < t.getHeight() - 10)
            {
                if(t.getHeight() - (snowHeight/30) > minHeight)
                {
                    //map.tiles.put(leftCoord, new Tile(t.getHeight() - (snowHeight/30)));
                    this.map.tiles[leftCoord.x][leftCoord.y] = new Tile(t.getHeight() - 10);
                    generationQueue.add(leftCoord);
                }
            }
        }
        if(rightCoord.x < map.length)
        {
            if(this.map.tiles[rightCoord.x][rightCoord.y] == null || this.map.tiles[rightCoord.x][rightCoord.y].getHeight() < t.getHeight() - 10)
            {
                if(t.getHeight() - (snowHeight/30) > minHeight)
                {
                    //map.tiles.put(rightCoord, new Tile(t.getHeight() - (snowHeight/30)));
                    this.map.tiles[rightCoord.x][rightCoord.y] = new Tile(t.getHeight() - 10);
                    generationQueue.add(rightCoord);
                }
            }
        }
    }
}